package com.googlecode.milib.tools.video {
	import com.googlecode.milib.base.MLBroadcastClass;
	import com.googlecode.milib.service.ISharedObjectsServicedObject;
	import com.googlecode.milib.service.SharedObjectsObjectService;
	import com.googlecode.milib.service.singleton.EnterFrameService;

	import flash.events.Event;
	import flash.events.NetStatusEvent;
	import flash.media.SoundTransform;
	import flash.media.Video;
	import flash.net.NetConnection;
	import flash.net.NetStream;
	import flash.net.URLRequest;
	import flash.utils.getTimer;

	/**
	 * @author Marek Brun
	 */
	public class VideoPlaybackModel extends MLBroadcastClass implements ISharedObjectsServicedObject {
		
		/** New streaming is started load. Video is playing. Event just after "startPlay" method.*/
		public static const event_VideoStart : String = 'VideoStart';		
		public static const event_NetStreamPlayStart : String = 'NetStreamPlayStart';
		
		/** Video playback reached end. */
		public static const event_VideoEnd : String = 'VideoEnd';
		
		/** Playback is started (from paused). */
		public static const event_PlayStart : String = 'PlayStart';
		
		/** Playback is paused. */
		public static const event_PlayStop : String = 'PlayStop';
		
		public static const event_VolumeChanged : String = 'VolumeChanged';
				public static const event_PlayingProgressChanged : String = 'PlayingProgressChanged';
				public static const event_LoadingProgressChanged : String = 'LoadingProgressChanged';
				public static const event_LoadFinish : String = 'LoadFinish';
				public static const event_BufferingStart : String = 'BufferingStart';
				public static const event_BufferingFinish : String = 'BufferingFinish';
		
		/** Meta data is laoded and accesible now. */		public static const event_RecivedMetaData : String = 'RecivedMetaData';
		
		/** Cant find streaming from passed URL. */
		public static const event_StreamNotFound : String = 'StreamNotFound';
		
		/**  */
		//DATA:	VideoCuePoint		public static const event_CuePoint : String = 'CuePoint';
		
		private var video:Video;
		private var connection:NetConnection;
		private var isBuffering:Boolean=true;
		public var metaData:VideoMetaData;
		private var netStream:NetStream;
		private var bufferTime:Number;
		private var volume:Number=.8;
		private var ef:EnterFrameService;
		private var isPlaying:Boolean;		public var isStartedStream:Boolean;
		private var lastLoadingProgress:Number;
		private var isBufferFlush:Boolean;
		private var isStarted:Boolean;
		private var lastNotifyTime:int;

		public function VideoPlaybackModel(video:Video, bufferTime:Number=5) {
			this.video=video;
			this.bufferTime=bufferTime;
			
			ef=EnterFrameService.getInstance();
			
			connection=new NetConnection();
			connection.connect(null);
			netStream=new NetStream(connection);
			netStream.bufferTime=5;
			netStream.addEventListener(NetStatusEvent.NET_STATUS, onStreamNetStatus, false, 0, true);
			netStream.client=this;
			video.attachNetStream(netStream);
			
			SharedObjectsObjectService.forInstance(this);
		}
		
		public function get event_SharedObjectDataChanged():String { return 'SharedObjectDataChanged'; }
		
		/**
		 * Start's streaming video from passed URL. Can be called anytime, many times.
		 */
		public function startPlay(fileURL:URLRequest):void {
			netStream.play(fileURL.url);
			
			metaData=null;
			isBuffering=false;			isBufferFlush=false;
			isStartedStream=true;
			
			refreshVolume();
			
			lastLoadingProgress=NaN;
			ef.addEventListener(ef.event_EnterFrame, onEnterFrameWhileLoading);
			
			isStarted=true;
			
			broadcastEvent(event_VideoStart);
			setIsBuffering(true);
		}
		
		/**
		 * Sets video volume. Volume is automatically stored in flash cookies.
		 * 
		 * @param volume - value from 0 to 1
		 */
		public function setVolume(volume:Number):void {
			if(this.volume==volume){ return; }			this.volume=volume;
			refreshVolume();
			broadcastEvent(event_SharedObjectDataChanged);
			broadcastEvent(event_VolumeChanged);
		}
		
		/**
		 * @return current volume
		 */
		public function getVolume():Number {
			return volume;
		}
		
		/**
		 * Move the playhead to nearest (keyframe) passed precentage position.
		 * Method will work only after meta data is loaded. 
		 * @param n01 - number from 0 to 1, desired position
		 */
		public function setPlayingProgress(n01:Number):void {
			if(metaData.duration){
				netStream.seek(n01*metaData.duration);
			}
		}
		
		/**
		 * Method will work only after meta data is loaded. 
		 * @return number from 0 to 1, current precentage position of the playhead
		 */
		public function getPlayingProgress():Number {
			if(metaData){
				return netStream.time/metaData.duration;
			}
			return NaN;
		}
		
		/**
		 * Method will work only after meta data is loaded. 
		 * @return number from 0 to 1, current playback precentage position
		 */
		public function getLoadingProgress():Number {
			if(isStarted){
				return netStream.bytesLoaded/netStream.bytesTotal;
			}
			return 0;
		}
		
		/**
		 * Moves video playhead to start kayframe and stop playing.
		 */
		public function revindFlvAndStop():void{
			if(!isStartedStream){ return; }
			netStream.seek(0);
			netStream.pause();
		}
		
		/**
		 * Moves video playhead to start kayframe and start playing.
		 */
		public function revindFlvAndPlay():void{
			if(!isStartedStream){ return; }
			netStream.seek(0);
			netStream.resume();
		}
		
		/**
		 * @return <ul><li>true - video playing time is less than one second</li>
		 * <li>false -  video playing time is bigger than one second</li></ul>
		 */
		public function isAtStart():Boolean{
			return netStream.time<1;
		}
		
		/**
		 * @return <ul><li>true - video playback is at end</li>
		 * <li>false -  video playback is not at end</li></ul>
		 */
		public function isAtEnd():Boolean{
			if(metaData){
				return Math.round(netStream.time)==Math.round(metaData.duration);
			}
			return false;
		}
		
		/**
		 * @return position of the playhead, in seconds
		 */
		public function getTime():Number {
			if(!netStream || isNaN(netStream.time)){ return NaN; }
			return netStream.time;
		}
		
		/**
		 * @return total time of the playhead, in seconds
		 */
		public function getDuration():Number {
			if(!metaData){ return NaN; }
			return metaData.duration;
		}
		
		/**
		 * @return current net stream
		 */
		public function getNetStream():NetStream {
			return netStream;
		}
		
		/**
		 * @return <ul><li>true - video is playing (not paused)</li>
		 * <li>false -  video is paused</li></ul>
		 */
		public function getIsPlaying():Boolean {
			return isPlaying;
		}
		
		/**
		 * Sets video playing status. Is current playing status is same as passed status - nothing hapens.
		 * If passed value is true, and video is stopped at end video is revinded to start and played.
		 * @param playing - <ul><li>true - video is playing (not paused)</li>
		 * <li>false -  video is paused</li></ul>
		 */
		public function setIsPlaying(playing:Boolean):void {
			if(!isStartedStream){ return; }
			if(isPlaying==playing){ return; }
			isPlaying=playing;
			if(playing){
				if(isAtEnd()){
					revindFlvAndPlay();
				}else{
					netStream.resume();
				}
				ef.addEventListener(ef.event_EnterFrame, onEnterFrameWhilePlaying);
				broadcastEvent(event_PlayStart);
			}else{
				netStream.pause();
				ef.removeEventListener(ef.event_EnterFrame, onEnterFrameWhilePlaying);
				broadcastEvent(event_PlayStop);
			}
		}
		
		/**
		 * @return <ul><li>true - video is currently buffering (and paused)</li>
		 * <li>false -  video is currently not buffering (there's enough data in buffer)</li></ul>
		 */
		public function getIsBuffering():Boolean {
			return isBuffering;
		}
		
		/**
		 * @return <ul><li>true - metadata of current video is loaded and accesible</li>
		 * <li>false -  metadata of current video is not yet loaded</li></ul>
		 */
		public function getGotMetaData():Boolean {
			return Boolean(metaData);
		}
		
		public function getMetaData():VideoMetaData {
			return metaData;
		}
		
		protected function refreshVolume():void {
			var videoVolumeTransform:SoundTransform = new SoundTransform();
			videoVolumeTransform.volume = volume;
			netStream.soundTransform = videoVolumeTransform;
		}
		
		protected function setIsBuffering(buffering:Boolean):void {
			if(isBuffering==buffering){ return; }
			if(buffering && isBufferFlush){ return; }
			isBuffering=buffering;
			if(isBuffering){
				broadcastEvent(event_BufferingStart);
			}else{
				broadcastEvent(event_BufferingFinish);
			}
		}
		
		override public function dispose():void {
			netStream.close();
			video=null;
			super.dispose();
		}
		
		public function getSharedObjectData():Object {
			return {
				volume:getVolume()
			};
		}
		
		public function getSharedObjectInitData():Object {
			return {
				volume:0.7
			};
		}
		
		public function getSharedObjectUniqueName():String {
			return 'VideoPlayback';
		}
		
		public function applyCookieData(data:Object):void {
			setVolume(data.volume);
		}
		
//********************************************************************************************
//		events for VideoPlayerModel
//********************************************************************************************
		protected function onStreamNetStatus(event:NetStatusEvent):void {
			var infoObject:Object=event.info;
			switch(infoObject.level){
				case 'error':
				break;
				case 'status':
					switch(infoObject.code){
						case 'NetStream.Play.Start':
							setIsPlaying(true);
							broadcastEvent(event_NetStreamPlayStart);
							break;
						case 'NetStream.Play.Stop':
							setIsPlaying(false);
							broadcastEvent(event_VideoEnd);
						break;
						case 'NetStream.Play.StreamNotFound':
							broadcastEvent(event_StreamNotFound);
						break;
						case 'NetStream.Buffer.Empty':
							setIsBuffering(true);
							break;
						case 'NetStream.Buffer.Full':
							setIsBuffering(false);
							break;
						case 'NetStream.Buffer.Flush':
							isBufferFlush=true;
							setIsBuffering(false);
						break;
						case 'NetStream.Seek.Notify':
							lastNotifyTime=getTimer();
							broadcastEvent(event_PlayingProgressChanged);
							ef.addEventListener(ef.event_EnterFrame, onEnterFrameAfterNotify);
							break;
					}
				break;
			}
		}
		
		protected function onEnterFrameWhilePlaying(event:Event):void {
			broadcastEvent(event_PlayingProgressChanged);
		}
		
		protected function onEnterFrameAfterNotify(event:Event):void {
			broadcastEvent(event_PlayingProgressChanged);
			if(getTimer()-lastNotifyTime>1000){
				ef.removeEventListener(ef.event_EnterFrame, onEnterFrameAfterNotify);
			}
		}
		
		public function onMetaData(info:Object):void {
			if(metaData){ return; }
			metaData=VideoMetaData.createByObject(info);
			broadcastEvent(event_RecivedMetaData);
		}
		
		public function onCuePoint(info:Object):void {
			broadcastEvent(event_CuePoint, metaData.getCuePointByTime(info.time*1000));
		}
		
		public function onPlayStatus(info:Object):void {
			
		}
		
		protected function onEnterFrameWhileLoading(event:Event):void {
			var loadingProgress:Number=getLoadingProgress();
			if(loadingProgress==lastLoadingProgress){ return; }			lastLoadingProgress=getLoadingProgress();
			broadcastEvent(event_LoadingProgressChanged);
			if(lastLoadingProgress==1){
				ef.removeEventListener(ef.event_EnterFrame, onEnterFrameWhileLoading);
				broadcastEvent(event_LoadFinish);
			}
		}
	}
}
